import 'dart:async';
import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

part 'Page.freezed.dart';

part 'Page.g.dart';

const pageStart = 1;
const defaultPageSize = 10;

@Freezed(genericArgumentFactories: true)
class Page<T> with _$Page<T> {
  const factory Page({
    required List<T> content,
    required bool last,
    required bool first,
    required int totalElements,
  }) = _Page<T>;

  const Page._();

  factory Page.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$PageFromJson(json, fromJsonT);

  factory Page.fromContent(List<T> content,Pageable pageable,int totalElements) => Page(
        content: content,
        last: content.length < pageable.size,
        first: pageable.page==pageStart,
        totalElements: totalElements,
      );
}

@Freezed(genericArgumentFactories: true)
class Slice<T> with _$Slice<T> {
  const factory Slice(
      {required List<T> content,
      required bool last,
      required bool first}) = _Slice;

  const Slice._();

  factory Slice.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$SliceFromJson(json, fromJsonT);

  factory Slice.fromOneMoreList(List<T> list, Pageable pageable) => Slice<T>(
        content: list.sublist(0, min(list.length, pageable.size)),
        last: list.length <= pageable.size,
        first: pageable.page == pageStart,
      );

  Slice<R> map<R>(R Function(T e) f) => Slice(
        content: content.map(f).toList(),
        last: last,
        first: first,
      );
}

extension SliceExtensions<T> on  Slice<T>{
  Future<Slice<R>> mapAsync<R>(Future<R> Function(T e) f) async {
    final newContent = await Future.wait(content.map(f));
    return Slice(
      content: newContent,
      last: last,
      first: first,
    );
  }
}

class PageableMergeOption {
  final int? page;
  final int? size;
  final Sort? sort;

  PageableMergeOption({this.page, this.size, this.sort});
}

class Pageable {
  final int page;
  final int size;
  final Sort sort;

  Pageable({this.page = pageStart, this.size = defaultPageSize, Sort? sort})
      : sort = sort ?? Sort.empty;

  factory Pageable.from(Pageable pageable, PageableMergeOption options) {
    return Pageable(
        page: options.page ?? pageable.page,
        size: options.size ?? pageable.size,
        sort: options.sort ?? pageable.sort);
  }

  copy(PageableMergeOption options) {
    return Pageable(
        page: options.page ?? page,
        size: options.size ?? size,
        sort: options.sort ?? sort);
  }

  Map<String, dynamic> toRequestParamsMap() {
    return {
      "page": page,
      "size": size,
      if (sort.orders.isNotEmpty) "sort": sort.toString(),
    };
  }

  Map<String, dynamic> toJson() {
    return toRequestParamsMap();
  }


  toDependencyList() {
    return [page, size, sort.toString()];
  }

  get offset => (page - 1) * size;

  get limit => size;
}

class Sort {
  final List<Order> orders;

  static final empty = Sort([]);

  Sort(this.orders);

  @override
  String toString() {
    return orders.join("|");
  }
}

class Order {
  final String name;
  final bool? ascend;

  Order(this.name, this.ascend);

  @override
  String toString() {
    return "$name${ascend == null ? "" : ",${ascend! ? "asc" : "desc"}"}";
  }
}

PagingController<PageKeyType, ItemType>
    usePagingController<PageKeyType, ItemType>({
  required final PageKeyType firstPageKey,
  final int? invisibleItemsThreshold,
  List<Object?>? keys,
  FutureOr<void> Function(PageKeyType pageKey,
          PagingController<PageKeyType, ItemType> pagingController)?
      onPageRequest,
}) {
  final controller = use(
    _PagingControllerHook<PageKeyType, ItemType>(
      firstPageKey: firstPageKey,
      invisibleItemsThreshold: invisibleItemsThreshold,
      keys: keys,
    ),
  );

  useEffect(() {
    listener(PageKeyType pageKey) => onPageRequest?.call(pageKey, controller);
    controller.addPageRequestListener(listener);
    return () => controller.removePageRequestListener(listener);
  }, [onPageRequest]);

  return controller;
}

class _PagingControllerHook<PageKeyType, ItemType>
    extends Hook<PagingController<PageKeyType, ItemType>> {
  const _PagingControllerHook({
    required this.firstPageKey,
    this.invisibleItemsThreshold,
    List<Object?>? keys,
  }) : super(keys: keys);

  final PageKeyType firstPageKey;
  final int? invisibleItemsThreshold;

  @override
  HookState<PagingController<PageKeyType, ItemType>,
          Hook<PagingController<PageKeyType, ItemType>>>
      createState() => _PagingControllerHookState<PageKeyType, ItemType>();
}

class _PagingControllerHookState<PageKeyType, ItemType> extends HookState<
    PagingController<PageKeyType, ItemType>,
    _PagingControllerHook<PageKeyType, ItemType>> {
  late final controller = PagingController<PageKeyType, ItemType>(
      firstPageKey: hook.firstPageKey,
      invisibleItemsThreshold: hook.invisibleItemsThreshold);

  @override
  PagingController<PageKeyType, ItemType> build(BuildContext context) =>
      controller;

  @override
  void dispose() => controller.dispose();

  @override
  String get debugLabel => 'usePagingController';
}
